using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Lime49;
using Lime49.UI;
using LockCrypt.Converters;
using LockCrypt.Core;
using LockCrypt.Properties;
using Microsoft.Win32;
using LockCrypt.DAL;

namespace LockCrypt {
    /// <summary>
    /// Allows a user to specify the database mode and enter connection details.
    /// </summary>
    public partial class DatabaseSelector : UserControl {
        private List<Constants.DatabaseType> _filterIndexes;

        public event EventHandler DatabasePathChanged;

        public static DependencyProperty IsSaveDialogProperty = DependencyProperty.Register("IsSaveDialog", typeof(bool), typeof(DatabaseSelector), new PropertyMetadata(true));
        /// <summary>
        /// Gets or sets whether a SaveFileDialog or an OpenFileDialog should be shown when browsing for a file. Default is <c>true</c>.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if a SaveFileDialog should be shown to browse for a file path, <c>false</c> to show an OpenFileDialog.
        /// </value>
        public bool IsSaveDialog {
            get { return System.Convert.ToBoolean(base.GetValue(IsSaveDialogProperty)); }
            set { base.SetValue(IsSaveDialogProperty, value); }
        }

        #region Database Dependency Properties
        public static DependencyProperty ConnectionStringProperty = DependencyProperty.Register("ConnectionString", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty, new PropertyChangedCallback(ConnectionString_Changed)));

        private static void ConnectionString_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            DatabaseSelector ctl = d as DatabaseSelector;
            string newConnectionString = Convert.ToString(e.NewValue);
            IDbProvider provider = DalConfigurationManager.GetProvider(ctl.DatabaseMode, ctl.EncryptionAlgorithm, ctl.EncryptionPassword, newConnectionString);
            if(provider is FileBasedDatabase) {
                FileBasedDatabase db = (FileBasedDatabase)provider;
                ctl.DatabasePath = db.FileName;
            } else if(provider is NetworkDatabase) {
                NetworkDatabase db = (NetworkDatabase)provider;
                ctl.DatabaseHost = db.Credentials.Hostname;
                ctl.DatabasePort = db.Credentials.Port;
                ctl.DatabaseUser = db.Credentials.Username;
                ctl.DatabasePassword = db.Credentials.Password;
                ctl.DatabaseName = db.Credentials.DatabaseName;
                ctl.DatabaseTablePrefix = db.Credentials.TablePrefix;
            }
        }

        /// <summary>
        /// Gets or sets the connection string for the chosen provider.
        /// </summary>
        /// <value>The connection string.</value>
        public string ConnectionString {
            set {
                base.SetValue(ConnectionStringProperty, value); 
            }
        }

        public static DependencyProperty AvailableDatabaseModesProperty = DependencyProperty.Register("AvailableDatabaseModes", typeof(Constants.DatabaseType), typeof(DatabaseSelector), new PropertyMetadata(Constants.DatabaseType.XML | Constants.DatabaseType.SQLite | Constants.DatabaseType.Legacy | Constants.DatabaseType.MySQL));
        /// <summary>
        /// Gets or sets the available database modes.
        /// </summary>
        /// <value>The available database modes.</value>
        public Constants.DatabaseType AvailableDatabaseModes {
            get { return (Constants.DatabaseType)base.GetValue(AvailableDatabaseModesProperty); }
            set {
                base.SetValue(AvailableDatabaseModesProperty, value);
                UpdateAvailableModes();
            }
        }

        public static DependencyProperty DatabaseModeProperty = DependencyProperty.Register("DatabaseMode", typeof(Constants.DatabaseType), typeof(DatabaseSelector), new PropertyMetadata(Constants.DatabaseType.XML));
        /// <summary>
        /// Gets the type of file selected.
        /// </summary>
        /// <value>The type.</value>
        public Constants.DatabaseType DatabaseMode {
            get { return (Constants.DatabaseType)base.GetValue(DatabaseModeProperty); }
            set { base.SetValue(DatabaseModeProperty, value); }
        }

        public static DependencyProperty AutoUpdateDatabasePathProperty = DependencyProperty.Register("AutoUpdateDatabasePath", typeof(bool), typeof(DatabaseSelector), new PropertyMetadata(true));
        /// <summary>
        /// Gets the wthether the name of the selected database file path should be automatically updated with the corrrect extension for the encryption and filetype selected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the name should be updated, otherwise, <c>false</c>.
        /// </value>
        public bool AutoUpdateDatabasePath {
            get { return Convert.ToBoolean(base.GetValue(AutoUpdateDatabasePathProperty)); }
            set { base.SetValue(AutoUpdateDatabasePathProperty, value); }
        }

        public static DependencyProperty DatabasePathProperty = DependencyProperty.Register("DatabasePath", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets the name of the selected database file.
        /// </summary>
        /// <value>The name of the selected database file.</value>
        public string DatabasePath {
            get { return Convert.ToString(base.GetValue(DatabasePathProperty)); }
            set { base.SetValue(DatabasePathProperty, value); }
        }

        public static DependencyProperty DatabasePathHistoryKeyProperty = DependencyProperty.Register("DatabasePathHistoryKey", typeof(string), typeof(DatabaseSelector), new PropertyMetadata("MiscFileHistory"));
        /// <summary>
        /// Gets or sets the name of the Settings value which stores the database history (which must be a string). The default is MiscFileHistory
        /// </summary>
        /// <value>The database path history key.</value>
        public string DatabasePathHistoryKey {
            get { return System.Convert.ToString(base.GetValue(DatabasePathHistoryKeyProperty)); }
            set { base.SetValue(DatabasePathHistoryKeyProperty, value); }
        }

        public static DependencyProperty DatabaseHostProperty = DependencyProperty.Register("DatabaseHost", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the database hostname.
        /// </summary>
        /// <value>The database hostname.</value>
        public string DatabaseHost {
            get { return System.Convert.ToString(base.GetValue(DatabaseHostProperty)); }
            set { base.SetValue(DatabaseHostProperty, value); }
        }

        /// <summary>
        /// Gets or sets the database port number.
        /// </summary>
        /// <value>The database port number.</value>
        public int DatabasePort {
            get { return System.Convert.ToInt32(DatabasePortString); }
            set { DatabasePortString = value.ToString(); }
        }

        public static DependencyProperty DatabasePortStringProperty = DependencyProperty.Register("DatabasePortString", typeof(string), typeof(DatabaseSelector), new PropertyMetadata("3306"));
        /// <summary>
        /// Gets or sets the database port number which the user entered before validation.
        /// </summary>
        /// <value>The database port number which the user entered before validation.</value>
        public string DatabasePortString {
            get { return System.Convert.ToString(base.GetValue(DatabasePortStringProperty)); }
            set { base.SetValue(DatabasePortStringProperty, value); }
        }

        public static DependencyProperty DatabaseUserProperty = DependencyProperty.Register("DatabaseUser", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the database username.
        /// </summary>
        /// <value>The database username.</value>
        public string DatabaseUser {
            get { return System.Convert.ToString(base.GetValue(DatabaseUserProperty)); }
            set { base.SetValue(DatabaseUserProperty, value); }
        }

        public static DependencyProperty DatabasePasswordProperty = DependencyProperty.Register("DatabasePassword", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the database connection password.
        /// </summary>
        /// <value>The database connection password.</value>
        public string DatabasePassword {
            get { return txtDBPassword.Password; }
            set {
                txtDBPassword.Password = value;
                base.SetValue(DatabasePasswordProperty, value);
            }
        }

        public static DependencyProperty DatabaseNameProperty = DependencyProperty.Register("DatabaseName", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the name of the database.
        /// </summary>
        /// <value>The name of the database.</value>
        public string DatabaseName {
            get { return System.Convert.ToString(base.GetValue(DatabaseNameProperty)); }
            set { base.SetValue(DatabaseNameProperty, value); }
        }

        public static DependencyProperty DatabaseTablePrefixProperty = DependencyProperty.Register("DatabaseTablePrefix", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the database table prefix.
        /// </summary>
        /// <value>The database table prefix.</value>
        public string DatabaseTablePrefix {
            get { return System.Convert.ToString(base.GetValue(DatabaseTablePrefixProperty)); }
            set { base.SetValue(DatabaseTablePrefixProperty, value); }
        }

        /// <summary>
        /// Gets or sets the provider which has been entered. This is not a backed property and is populated from the user specified values.
        /// </summary>
        /// <value>The provider.</value>
        public IDbProvider Provider {
            get { 
                IDbProvider currentProvider;
                if(DalConfigurationManager.IsProviderFileBased(DatabaseMode)) {
                    currentProvider = DalConfigurationManager.GetProvider(DatabaseMode, EncryptionAlgorithm, EncryptionPassword, DatabasePath);
                } else {
                    NetworkDbCredentials credentials = new NetworkDbCredentials(DatabaseHost, DatabasePort, DatabaseUser, DatabasePassword, DatabaseName, DatabaseTablePrefix);
                    currentProvider = DalConfigurationManager.GetProvider(DatabaseMode, EncryptionAlgorithm, EncryptionPassword, credentials);
                }
                return currentProvider;
            }
            set {
                DatabaseMode = value.DatabaseType;
                if(value is FileBasedDatabaseProvider) {
                    var db = value as FileBasedDatabaseProvider;
                    DatabasePath = db.FileName;
                } else if(value is NetworkDatabase) {
                    var db = value as NetworkDatabase;
                    DatabaseHost = db.Credentials.Hostname;
                    DatabasePort = db.Credentials.Port;
                    DatabaseUser = db.Credentials.Username;
                    DatabasePassword = db.Credentials.Password;
                    DatabaseName = db.Credentials.DatabaseName;
                    DatabaseTablePrefix = db.Credentials.TablePrefix;
                }
                EncryptionAlgorithm = value.Encryption.EncryptionAlgorithm;
            }
        }
        #endregion

        #region Encryption Properties
        public static DependencyProperty CanEditEncryptionProperty = DependencyProperty.Register("CanEditEncryption", typeof(bool), typeof(DatabaseSelector), new PropertyMetadata(true));
        /// <summary>
        /// Gets or sets a value indicating whether the user can edit encryption options (this controls the IsEnabled state of the encryption options).
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the user can edit encryption options, otherwise <c>false</c>.
        /// </value>
        public bool CanEditEncryption {
            get { return System.Convert.ToBoolean(base.GetValue(CanEditEncryptionProperty)); }
            set { base.SetValue(CanEditEncryptionProperty, value); }
        }

        public static DependencyProperty ShowEncryptionProperty = DependencyProperty.Register("ShowEncryption", typeof(bool), typeof(DatabaseSelector), new PropertyMetadata(true));
        /// <summary>
        /// Gets or sets a value indicating whether the encryption options should be shown (this controls the Visibility of the encryption options).
        /// </summary>
        /// <value><c>true</c> if encryption options should be shown, otherwise <c>false</c>.</value>
        public bool ShowEncryption {
            get { return System.Convert.ToBoolean(base.GetValue(ShowEncryptionProperty)); }
            set { base.SetValue(ShowEncryptionProperty, value); }
        }

        public static DependencyProperty ShowPasswordConfirmationProperty = DependencyProperty.Register("ShowPasswordConfirmation", typeof(bool), typeof(DatabaseSelector), new PropertyMetadata(true));
        /// <summary>
        /// Gets or sets whether the 'Confirm Password' field is shown and (if applicable) validated.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the 'Confirm Password' field is shown, otherwise <c>false</c>.
        /// </value>
        public bool ShowPasswordConfirmation {
            get { return System.Convert.ToBoolean(base.GetValue(ShowPasswordConfirmationProperty)); }
            set { base.SetValue(ShowPasswordConfirmationProperty, value); }
        }

        public static DependencyProperty EncryptionAlgorithmProperty = DependencyProperty.Register("EncryptionAlgorithm", typeof(EncryptionProviders), typeof(DatabaseSelector), new PropertyMetadata(EncryptionProviders.None));
        /// <summary>
        /// Gets the selected encryption algorithm.
        /// </summary>
        /// <value>The selected encryption algorithm.</value>
        public EncryptionProviders EncryptionAlgorithm {
            get { return (EncryptionProviders)base.GetValue(EncryptionAlgorithmProperty); }
            set { base.SetValue(EncryptionAlgorithmProperty, value); }
        }

        public static DependencyProperty EncryptionPasswordProperty = DependencyProperty.Register("EncryptionPassword", typeof(string), typeof(DatabaseSelector), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets or sets the encryption password.
        /// </summary>
        /// <value>The encryption password.</value>
        public string EncryptionPassword {
            get { return txtPassword.Password; }
            set { 
                txtPassword.Password = value;
                base.SetValue(EncryptionPasswordProperty, value);
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new <see cref="DatabaseSelector"/>.
        /// </summary>
        public DatabaseSelector() {
            InitializeComponent();
            cboEncryption.ItemsSource = EnumBinder.EnumHelper.ToList<string>(typeof(EncryptionProviders));
        }

        /// <summary>
        /// Initializes the OpenFileDialog/SaveFileDialog when the Window loads and adds available database types.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e) {
            UpdateAvailableModes();
            UpdateFileExtension();
        }

        /// <summary>
        /// Updates the available database modes in the save dialog and comboboxes.
        /// </summary>
        private void UpdateAvailableModes() {
            if(!IsLoaded)
                return;
            StringBuilder filterBuffer = new StringBuilder(string.Format("{0}|*.*", I18nUtils.GetString("Strings", "AllFiles")));
            Dictionary<Constants.DatabaseType, string> availableTypes = new Dictionary<Constants.DatabaseType, string>();
            _filterIndexes = new List<Constants.DatabaseType>();
            string defaultExt = string.Empty;
            int selectedFilterIndex = 1;
            if((AvailableDatabaseModes & Constants.DatabaseType.XML) == Constants.DatabaseType.XML) {
                availableTypes.Add(Constants.DatabaseType.XML, I18nUtils.GetString("Strings", "XmlFile"));
                _filterIndexes.Add(Constants.DatabaseType.XML);
                filterBuffer.AppendFormat("|{0}|*.lce;*.xml", I18nUtils.GetString("Strings", "XmlFiles"));
                defaultExt = ".lce";
                selectedFilterIndex = _filterIndexes.Count - 1;
            }
            if((AvailableDatabaseModes & Constants.DatabaseType.SQLite) == Constants.DatabaseType.SQLite) {
                availableTypes.Add(Constants.DatabaseType.SQLite, I18nUtils.GetString("Strings", "SQLiteDatabase"));
                _filterIndexes.Add(Constants.DatabaseType.SQLite);
                filterBuffer.AppendFormat("|{0}|*.lce;*.db3", I18nUtils.GetString("Strings", "SQLiteDatabases"));
                defaultExt = ".lce";
                selectedFilterIndex = _filterIndexes.Count - 1;
            }
            if((AvailableDatabaseModes & Constants.DatabaseType.MySQL) == Constants.DatabaseType.MySQL) {
                availableTypes.Add(Constants.DatabaseType.MySQL, "MySQL");
            }
            if((AvailableDatabaseModes & Constants.DatabaseType.Legacy) == Constants.DatabaseType.Legacy) {
                availableTypes.Add(Constants.DatabaseType.Legacy, I18nUtils.GetString("Strings", "LegacyDatabase"));
                _filterIndexes.Add(Constants.DatabaseType.Legacy);
                filterBuffer.AppendFormat("|{0} (*.lce)|*.lce", I18nUtils.GetString("Strings", "LegacyDatabase"));
                defaultExt = ".lce";
                selectedFilterIndex = _filterIndexes.Count - 1;
            }
            dbPathChooser.FilterString = filterBuffer.ToString();
            dbPathChooser.FilterIndex = selectedFilterIndex;
            dbPathChooser.DefaultExtension = defaultExt;
            cboDbMode.ItemsSource = availableTypes;
        }

        /// <summary>
        /// Validates user input then returns a DialogResult of true.
        /// </summary>
        /// <returns></returns>
        public bool ValidateSettings() {
            return ValidateSettings(false);
        }

        /// <summary>
        /// Validates user input then returns a DialogResult of true.
        /// </summary>
        /// <param name="allowEmpty"><c>true</c> to allow validation if all fields are empty, <c>false</c> to validate both completeness and empty values.</param>
        /// <returns></returns>
        public bool ValidateSettings(bool allowEmpty) {
            bool valid = true;
            Window parentWindow = Window.GetWindow(this);
            if(DalConfigurationManager.IsProviderFileBased(DatabaseMode)) {
                // flat files
                if(string.IsNullOrEmpty(DatabasePath) && !allowEmpty) {
                    DialogBox.ShowAlert(parentWindow, I18nUtils.GetString("Errors", "DatabasePathRequired"), I18nUtils.GetString("Errors", "Error"));
                    txtDBHost.Focus();
                    valid = false;
                } else {
                    try {
                        Path.GetFullPath(DatabasePath); // throws exception on invalid path
                    } catch {
                        DialogBox.ShowAlert(parentWindow, I18nUtils.GetString("Errors", "DatabasePathRequired"), I18nUtils.GetString("Errors", "Error"));
                        txtDBHost.Focus();
                        valid = false;
                    }
                }
            } else {
                int dbPort = 0;
                bool hostValid = !string.IsNullOrEmpty(DatabaseHost),
                     portValid = int.TryParse(DatabasePortString, out dbPort),
                     userValid = !string.IsNullOrEmpty(DatabaseUser),
                     nameValid = !string.IsNullOrEmpty(DatabaseName);
                if(!hostValid && !portValid && !userValid && !nameValid && allowEmpty) {
                    // allow to continue, all fields empty
                } else if(!hostValid) {
                    DialogBox.Show(parentWindow, I18nUtils.GetString("Errors", "DatabaseHostnameRequired"), I18nUtils.GetString("Errors", "Error"), DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    txtDBHost.Focus();
                    valid = false;
                } else if(!portValid) {
                    DialogBox.Show(parentWindow, I18nUtils.GetString("Errors", "StringRangeFmt", I18nUtils.GetString("Strings", "DatabasePort"), "1", "65536"), I18nUtils.GetString("Errors", "Error"), DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    txtDBPort.Focus();
                    valid = false;
                } else if(!userValid) {
                    DialogBox.Show(parentWindow, I18nUtils.GetString("Errors", "UsernameRequired"), I18nUtils.GetString("Errors", "Error"), DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    txtDBUser.Focus();
                    valid = false;
                } else if(!nameValid) {
                    DialogBox.Show(parentWindow, I18nUtils.GetString("Errors", "DatabaseNameRequired"), I18nUtils.GetString("Errors", "Error"), DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    txtDBName.Focus();
                    valid = false;
                }
            }
            if(valid && IsSaveDialog && ShowEncryption && CanEditEncryption && ShowPasswordConfirmation) {
                if(txtPassword.Password != txtConfirmPassword.Password) {
                    DialogBox.ShowAlert(parentWindow, I18nUtils.GetString("Errors", "PasswordsDontMatch"), I18nUtils.GetString("Errors", "Error"));
                    txtPassword.Focus();
                    valid = false;
                } else if(txtPassword.Password.Trim().Length == 0 && DialogBox.Show(parentWindow, I18nUtils.GetString("Errors", "PassBlankDesc"), I18nUtils.GetString("Errors", "PasswordBlank"), DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question, DialogBoxButton.Yes) != DialogBoxButton.Yes) {
                    txtPassword.Focus();
                    valid = false;
                }
            }
            
            return valid;
        }

        /// <summary>
        /// Disables modification of the encryption algorithm when legacy encryption is selected.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void cboDbMode_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(DatabaseMode != Constants.DatabaseType.Legacy) {
                cboEncryption.IsEnabled = true;
            } else {
                EncryptionAlgorithm = EncryptionProviders.Aes256;
                cboEncryption.IsEnabled = false;
            }
            UpdateFileExtension();
        }

        private void ccboEncryption_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            UpdateFileExtension();
        }

        /// <summary>
        /// Updates the file extension for the file picker and combobox.
        /// </summary>
        private void UpdateFileExtension() {
            if(!IsLoaded || !IsEnabled || !AutoUpdateDatabasePath)
                return;
            dbPathChooser.FilterIndex = _filterIndexes.IndexOf(DatabaseMode) +2;
            switch(DatabaseMode) {
                case Constants.DatabaseType.SQLite:
                    dbPathChooser.DefaultExtension = ".lce";
                    break;
                case Constants.DatabaseType.Legacy:
                    dbPathChooser.DefaultExtension = ".lce";
                    break;
                case Constants.DatabaseType.XML:
                default:
                    if(EncryptionAlgorithm == EncryptionProviders.None) {
                        dbPathChooser.DefaultExtension = ".xml";
                    } else {
                        dbPathChooser.DefaultExtension = ".lce";
                    }
                    break;
            }
            try {
                if(DatabasePath.Length == 0)
                    return;
                string currentExtension = System.IO.Path.GetExtension(DatabasePath);
                string currentFilename = DatabasePath.Substring(0, DatabasePath.Length - currentExtension.Length);
                DatabasePath = currentFilename + "." + dbPathChooser.DefaultExtension;
            } catch {
            }
        }

        private void dbPathSelector_PathChanged(object sender, EventArgs e) {
            if(DatabasePathChanged != null)
                DatabasePathChanged(this, EventArgs.Empty);
        }
    }
}
